package com.pc.cloud.core;

import com.pc.cloud.services.DocumentBuilder;
import com.pc.cloud.services.SearchArgs;
import com.pc.cloud.services.SearchResults;
import com.pc.cloud.services.impl.DefaultDocumentBuilder;
import com.pc.cloud.utils.DocumentUtils;
import com.pc.cloud.utils.IndexHomeUtils;
import org.json.JSONObject;

import java.io.*;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

public class SearchHandler {
	private static final Object OUTPUT_MUTEX = new Object();
	private ObjectOutputStream output;
	Map<String, SearchManager> searchManagers = new HashMap<String, SearchManager>();
	private Map<String,Boolean> indexOnFile = new HashMap<String, Boolean>();

	public void setIndexOnFile(Map<String, Boolean> indexOnFile) {
		this.indexOnFile = indexOnFile;
	}

	public void init() throws Exception {
		try {
			this.rescueIndex();
		} catch (Exception e) {
			e.printStackTrace();
			throw e;
		}

		new Thread(new CommitWorker()).start();
	}

	public void index(DocumentBuilder documentBuilder) throws Exception {
		this.indexDocument(documentBuilder);
		this.saveDocument(documentBuilder);
	}

	private void indexDocument(DocumentBuilder documentBuilder) throws Exception {
		String searchType = documentBuilder.getSearchType().toString();
		try {
			SearchManager sm = getSearchManager(searchType);
			sm.createIndex(documentBuilder.getDocs(), documentBuilder.getKeys());
		} catch (Exception e) {
			e.printStackTrace();
			throw e;
		}
	}

	private synchronized SearchManager getSearchManager(String searchType) throws Exception {
		SearchManager sm = searchManagers.get(searchType);
		if (sm == null) {
			Boolean isOnFile = this.indexOnFile.get(searchType);
			sm = SearchManagerFactory.createSearchManager(searchType,isOnFile);
			searchManagers.put(searchType, sm);
		}
		return sm;
	}

	
	
	public SearchResults search(SearchArgs args) {
		SearchResults sr = null;
		String searchType = args.getSearchType().toString();
		try {
			SearchManager sm = getSearchManager(searchType);
			sr = sm.search(args);
		} catch (Exception e) {
			e.printStackTrace();
		}

		return sr;
	}

	private void rescueIndex() throws Exception {
		// rebuild the uncommitted indexes
		long a = System.currentTimeMillis();
		File commitFile = this.getUnCommitedFile();
		if (commitFile.exists()) {
			FileInputStream fis = null;
			BufferedInputStream bis = null;
			ObjectInputStream ois = null;
			try {
				fis = new FileInputStream(commitFile);
				bis = new BufferedInputStream(fis);
				ois = new ObjectInputStream(bis);
				String jpd = (String) ois.readObject();
				DefaultDocumentBuilder db = new DefaultDocumentBuilder(new JSONObject(jpd));
				while (db != null) {
					System.out.println("rebuild : " + db.getKeys().size());
					this.indexDocument(db);
					jpd = (String) ois.readObject();
					db = new DefaultDocumentBuilder(new JSONObject(jpd));
				}

			} catch (EOFException e) {
				System.out.println("end of commitFile");
			} finally {
				if (ois != null) {
					ois.close();
				}
				if (bis != null) {
					bis.close();
				}
				if (fis != null) {
					fis.close();
				}
			}
		}

		long b = System.currentTimeMillis();
		System.out.println("rescue Indes took:" + (b - a) + "ms");
		this.commit();
	}

	private void createNewOutputStream() throws Exception {
		File commitFile = this.getUnCommitedFile();
		commitFile.delete();
		FileOutputStream fos = new FileOutputStream(commitFile, false);
		BufferedOutputStream bfs = new BufferedOutputStream(fos);
		this.output = new ObjectOutputStream(bfs);
	}

	public void saveDocument(DocumentBuilder documentBuilder) throws Exception {
		synchronized (OUTPUT_MUTEX) {
			this.output.writeObject(DocumentUtils.toJSON(documentBuilder).toString());
			this.output.flush();
		}
	}

	private File getUnCommitedFile() throws Exception {
        String catalog = IndexHomeUtils.getPenguinCatalog();
		System.out.println("catalog dir : " + catalog);
		File dir = new File(catalog);
		File commitFile = new File(catalog + File.separatorChar +  "uncommitedIndex.log");
		if (!dir.exists()) {
			dir.mkdirs();
		}
		return commitFile;
	}

	public void close() throws Exception {
		this.commit();
	}

	public String commit() throws Exception {
		synchronized (OUTPUT_MUTEX) {
			StringBuilder sb = new StringBuilder();
			for (Entry<String, SearchManager> entry : searchManagers.entrySet()) {
				entry.getValue().commit();
				sb.append(entry.getKey());
				sb.append(" has commited <br>");
			}
			this.createNewOutputStream();
			return sb.toString();
		}
	}

	class CommitWorker implements Runnable {
		public CommitWorker() {
		}
		public void run() {
			while (true) {
				try {
					// commit the indexes per 30 minutes
					Thread.sleep(30 * 60 * 1000);
					System.out.println("CommitWorker commit " + new Date());
					long a = System.currentTimeMillis();
					String message = commit();
					System.out.println(message);
					System.out.println("indexes have commited, spent " + (System.currentTimeMillis() - a) + "ms");
				} catch (Throwable t) {
					t.printStackTrace();
				}
			}
		}
	}
}
